﻿using System;
using System.Collections.Generic;
using System.Text.RegularExpressions;
using ICSharpCode.AvalonEdit.Document;
using ICSharpCode.AvalonEdit.Folding;

namespace CryForLua_wpf_AvEd
{
    public class LuaFolding : AbstractFoldingStrategy
    {
        private TextDocument m_TextDocument = null;

        public override IEnumerable<NewFolding> CreateNewFoldings(TextDocument document, out int firstErrorOffset)
        {
            m_TextDocument = document;
            firstErrorOffset = -1;
            return CreateNewFoldings(document);
        }
        public IEnumerable<NewFolding> CreateNewFoldings(ITextSource document)
        {
            List<NewFolding> newFoldings = new List<NewFolding>();

            if (document.Text == "")
            {
                return newFoldings;
            }

            MainWindow.ShowStatusBarMessage("Updating foldings...");

            FindBlocks(ref newFoldings, document.Text);

            FindBracketFoldings(ref newFoldings, document.Text);

            FindRegions(ref newFoldings, document.Text);

            try
            {
                newFoldings.Sort((a, b) => a.StartOffset.CompareTo(b.StartOffset));
            }
            catch (Exception e)
            {
                return null;
            }
            return newFoldings;
        }

        private void FindBracketFoldings(ref List<NewFolding> foldings, string sText)
        {
            List<CodeAnalysing.LuaCodeRegion> list = CodeAnalysing.CodeAnalyser.FindTablesInCode(sText);

            for (int i = 0; i < list.Count; ++i)
            {
                foldings.Add(new NewFolding(list[i].m_startAt, list[i].m_endAt));
            }

            /*
            // If there are no brackets at all do nothing
            if (sText.IndexOf('{') == -1)
                return;

            int nClosingBracketPos = 0;

            int nOpeningBracketPos = 0;

            // Used for nestet stuff
            List<int> usedPositions = new List<int>();

            while (true)
            {
                nClosingBracketPos = sText.IndexOf('}', nClosingBracketPos + 1);

                if (nClosingBracketPos == -1)
                    break;

                if (IsCommentedOut(nClosingBracketPos, sText))
                    continue;

                // Search the first former { to the already found }
                nOpeningBracketPos = sText.LastIndexOf('{', nClosingBracketPos);

                if (nOpeningBracketPos == -1)
                    return;

               

                while (usedPositions.Contains(nOpeningBracketPos) || IsCommentedOut(nOpeningBracketPos, sText))
                {

                  
                    // Position is already used meaning that the corresponding { is 
                    //even closer to the beginning of the text
                    nOpeningBracketPos = sText.LastIndexOf('{', nOpeningBracketPos - 1);

                    if (nOpeningBracketPos == -1)
                        return;
                }

                // A valid position has been found, add to the used 
                usedPositions.Add(nOpeningBracketPos);

                // +1 so the } is inlcuded in the folding
                foldings.Add(new NewFolding(nOpeningBracketPos, nClosingBracketPos + 1));
            }
             * */
        }

        private void FindBlocks(ref List<NewFolding> foldings, string sText)
        {
            List<CodeAnalysing.LuaCodeBlock> blocks = CodeAnalysing.CodeAnalyser.FindBlocks(sText);

            foreach (CodeAnalysing.LuaCodeBlock block in blocks)
            {
                foldings.Add(new NewFolding(AdjustFoldingPosition(block, sText), block.m_endAt));
            }

          /*  if (sText.IndexOf("end") == -1)
                return;

            int nClosingBracketPos = 0;

            int nFunctionPos = 0;
            int nIfPos = 0;
            int nDoPos = 0;
            int closestPos = 0;
            int finalPos = 0;
           

            // Used for nestet stuff
            List<int> usedPositions = new List<int>();

            while (true)
            {
                nClosingBracketPos = sText.IndexOf("end", nClosingBracketPos + 1);
                nClosingBracketPos = GetNextValidKeyword(nClosingBracketPos, "end", sText, false);

                if (nClosingBracketPos == -1)
                    break;

                if (IsCommentedOut(nClosingBracketPos, sText))
                    continue;

                closestPos = nClosingBracketPos;

                do
                {
                    nFunctionPos = sText.LastIndexOf("function", Math.Max(closestPos - 1, 0));
                    nFunctionPos = GetNextValidKeyword(nFunctionPos, "function", sText);

                    nIfPos = sText.LastIndexOf("if", Math.Max(closestPos - 1, 0));
                    nIfPos = GetNextValidKeyword(nIfPos, "if", sText);

                    nDoPos = sText.LastIndexOf("do", Math.Max(closestPos - 1, 0));
                    nDoPos = GetNextValidKeyword(nDoPos, "do", sText);

                    closestPos = Math.Max(Math.Max(nFunctionPos, nDoPos), nIfPos);

                    if (closestPos == -1)
                        break;


                    finalPos = AdjustFoldingPosition(closestPos, nFunctionPos, nDoPos, nIfPos, sText);
                } while (IsCommentedOut(closestPos, sText) || usedPositions.Contains(finalPos));

                if (closestPos == -1)
                    break;

                if (finalPos > nClosingBracketPos)
                    finalPos = closestPos;

                usedPositions.Add(finalPos);


                foldings.Add(new NewFolding(finalPos, nClosingBracketPos + "end".Length));
            }
           * */
        }

        private int AdjustFoldingPosition(CodeAnalysing.LuaCodeBlock block, string sText)
        {
            int finalPos = block.m_startAt;


            switch (block.m_nType)
            {
                case CodeAnalysing.LuaCodeBlock.BlockType.eBT_Function:
                    {
                        int functionHeadEnd = sText.IndexOf(')', block.m_startAt);

                        finalPos = functionHeadEnd + 1;

                    }break;
                case CodeAnalysing.LuaCodeBlock.BlockType.eBT_IfCondition:
                    {
                        int thenPos = sText.IndexOf("then", block.m_startAt);
                            
                         thenPos = CodeAnalysing.CodeAnalyser.GetNextValidKeyword(block.m_startAt, "then", sText, false);

                        if (thenPos != -1)
                            finalPos = thenPos;
                    }break;
                case CodeAnalysing.LuaCodeBlock.BlockType.eBT_DoBlock:
                    {
                    }break;
                default:
                    {
                    }break;
                    
            };


            return finalPos;
        }

        private int CheckIsKeyWord(int posToCheck, string sKeyWord, string sText)
        {
            if (posToCheck == -1)
                return -1;

            // Check before the supposed keyword
            if (posToCheck > 0)
            {
                // If its 0 there's no sense in checking before it
                char chBeforeKW = sText[posToCheck - 1];

                // If there's no whitespace before it, it's not a keyword
                if (!Char.IsWhiteSpace(chBeforeKW))
                {
                    // Except if its a 'if()then' construct
                    if (sKeyWord != "then")
                    {
                        return -1;
                    }
                    else
                    {
                        if (chBeforeKW != ')')
                        {
                            return -1;
                        }
                    }
                }
            }

            // Check behind the supposed Keyword

            int checkPos = posToCheck + sKeyWord.Length;

            // If the character after the keyword doesn't exist, assume it is a keyword
            if (checkPos >= sText.Length)
                return posToCheck;

            char chAfterKW = sText[checkPos];


            if (!Char.IsWhiteSpace(chAfterKW) && chAfterKW != ';')
            {
                if (sKeyWord == "if" || sKeyWord == "function")
                {
                    // These need special treating since function and if clauses can be followed directly by parenthesises

                    if (chAfterKW == '(')
                    {
                        // No whitespace but parenthesis == keyword
                        return posToCheck;
                    }
                    else
                    {
                        // No parenthesis and no whitespace == no keyword
                        return -1;
                    }
                }
                else if (sKeyWord == "end")
                {
                    if (chAfterKW == ',')
                        return posToCheck;
                    else
                        return -1;
                }
              

                // No special keyword and no whitespace == no keyword

                return -1;
            }


            // Whitespaces before and after the keyword == keyword

            return posToCheck;
        }

        private int GetNextValidKeyword(int startingPos, string sKeyword, string sText, bool bSearchBackwards = true)
        {
            int lastCheckedPos = startingPos;
            // If there are no keywords at all, don't do anything
            if (startingPos != -1)
            {
                // Check if its a keyword
                while ((startingPos = CheckIsKeyWord(startingPos, sKeyword, sText)) == -1)
                {
                    // No, so search the next occurence
                    if (bSearchBackwards)
                        startingPos = sText.LastIndexOf(sKeyword, lastCheckedPos - 1);
                    else
                        startingPos = sText.IndexOf(sKeyword, lastCheckedPos + 1);
                    // No more occurences? Break.
                    if (startingPos == -1)
                        return -1;

                    lastCheckedPos = startingPos;
                }
            }
            else
                return -1;

            return startingPos;
        }

        private bool IsCommentedOut(int pos, string sText)
        {
            return CodeAnalysing.CodeAnalyser.IsCommentedOut(pos, sText);
            /*
            bool bIsCommentedOut = false;

            // We'll start with single line comments as they are far more common than block comments

            DocumentLine line = m_TextDocument.GetLineByOffset(pos);

            string lineText = m_TextDocument.GetText(line);

            int commentPos = lineText.IndexOf("--");


            if (commentPos != -1)
            {
                commentPos += line.Offset;

                if (commentPos > pos)
                    bIsCommentedOut = false;
                else
                    bIsCommentedOut = true;

                // If it's commented out we can stop
                if (bIsCommentedOut)
                    return bIsCommentedOut;

            }
            // Up until here we only checked single line comments

            int lastBlockCommentStart = sText.LastIndexOf("--[[", pos);
            int lastBlockCommetnEnd = sText.LastIndexOf("]]", pos);

            if (lastBlockCommentStart > lastBlockCommetnEnd)
                bIsCommentedOut = true;     
            else
                bIsCommentedOut = false;  // also takes the absence of blockcomments into account

            return bIsCommentedOut;*/
        }

        private void FindRegions(ref List<NewFolding> foldings, string sText)
        {

            int nStartPos = 0;
            int nEndPos = 0;
            List<int> usedPositions = new List<int>();

            
            while (true)
            {
                nEndPos = sText.IndexOf("--#endregion", nEndPos);

                if (nEndPos == -1)
                    break;

                nStartPos = nEndPos;

                do
                {
                    nStartPos = sText.LastIndexOf("--#region", nStartPos - 1);

                    if (nStartPos == -1)
                        return;

                } while (usedPositions.Contains(nStartPos));


                DocumentLine line = m_TextDocument.GetLineByOffset(nEndPos);

                nEndPos = line.Offset + line.Length;

                line = m_TextDocument.GetLineByOffset(nStartPos);

                int finalStart = 0;

                finalStart = line.Length + line.Offset;


                NewFolding folding = null;

                try
                {
                    folding = new NewFolding(finalStart, nEndPos);
                }
                catch (Exception e)
                {
                    return;
                }


                foldings.Add(folding);
            }

            


        }
    }
}